package gitee.derumin.lib_for_derumin.helpers.config;

import com.google.gson.*;
import gitee.derumin.lib_for_derumin.helpers.config.configserializers.ConfigFieldSerializer;
import gitee.derumin.lib_for_derumin.logger.DeruminLogger;
import net.minecraft.util.JsonHelper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;

@ApiStatus.Internal
public class ConfigManager implements ConfigController {
    private static final DeruminLogger LOGGER = DeruminLogger.getLogger();
    private static final List<ConfigFieldSerializer<?>> SERIALIZERS = new ArrayList<>();
    private final Object configInstance;
    private final String configName;
    private final Map<Field, Parser> parserMap;

    static void addAvailableSerializer(ConfigFieldSerializer<?> serializer){
        boolean bl = true;
        for (ConfigFieldSerializer<?> serializer1 : SERIALIZERS){
            if (serializer1.getClass().equals(serializer.getClass())){
                bl = false;
                break;
            }
        }
        if (bl){
            SERIALIZERS.add(serializer);
        }
    }

    ConfigManager(Object config, String configName){
        this.configInstance = config;
        this.configName = configName;
        this.parserMap = new HashMap<>();
        analysis();
    }

    void analysis(){
        Field[] fields = this.configInstance.getClass().getFields();
        for (Field field : fields){
            try {
                field.setAccessible(true);
                Object obj = field.get(this.configInstance);
                Parser parser = Parser.seekParser(obj, this);
                if (parser != null){
                    this.parserMap.put(field, parser);
                }else {
                    LOGGER.error("[Derumin Config Helper]Cant serialize the field: '" + field + "'!", DeruminLogger.AnnounceType.OPS);
                }
            } catch (Exception e) {
                declareSerializeFailure(e, field.getName(), DeruminLogger.AnnounceType.ALL);
            }
        }
    }

    public String getConfigName(){
        return this.configName;
    }

    @Override
    public boolean modifyFrom(@NotNull JsonObject object){
        boolean bl = false;
        for (Map.Entry<Field, Parser> entry : this.parserMap.entrySet()){
            Field field = entry.getKey();
            String name = field.getName();
            if (object.has(name)){
                try {
                    field.setAccessible(true);
                    field.set(this.configInstance, entry.getValue().readJson(JsonHelper.getElement(object, name), name));
                    bl = true;
                } catch (Exception e) {
                    declareDeserializeFailure(e, name, DeruminLogger.AnnounceType.OPS);
                }
            }
        }
        return bl;
    }

    @Override
    public void initializeFromJson(JsonObject object){
        if (object != null) {
            this.parserMap.forEach(((field, parser) -> {
                String name = field.getName();
                if (object.has(name)){
                    try {
                        field.setAccessible(true);
                        field.set(this.configInstance, parser.readJson(JsonHelper.getElement(object, name), name));
                    } catch (Exception e) {
                        declareDeserializeFailure(e, field.getName(), DeruminLogger.AnnounceType.ALL);
                    }
                }else {
                    declareJsonMissing(name);
                }
            }));
        }
    }

    @Override
    public JsonObject writeConfigToJson(){
        JsonObject object = new JsonObject();
        this.parserMap.forEach((field, parser) -> {
            try {
                object.add(field.getName(), parser.writeJson(field.get(this.configInstance)));
            } catch (Exception e) {
                declareSerializeFailure(e, field.getName(), DeruminLogger.AnnounceType.OPS);
            }
        });
        return object;
    }

    @Override
    public @NotNull JsonObject getDefaultJson() {
        JsonObject object = new JsonObject();
        this.parserMap.forEach((field, parser) -> object.add(field.getName(), parser.defaultValue));
        return object;
    }

    void declareSerializeFailure(Exception e, String fieldName, DeruminLogger.AnnounceType type){
        LOGGER.error("[Derumin Config Helper]Failed to serialize config entry '" +fieldName + "': " + e.getMessage(), type);
    }

    void declareDeserializeFailure(Exception e, String fieldName, DeruminLogger.AnnounceType type){
        LOGGER.error("[Derumin Config Helper]Failed to deserialize config entry '" +fieldName + "': " + e.getMessage(), type);
    }

    void declareJsonMissing(String fieldName){
        LOGGER.error("[Derumin Config Helper]Config entry: " + fieldName + " in " + getConfigName() + " is missing!", DeruminLogger.AnnounceType.OPS);
    }

    private class Parser {
        protected final JsonElement defaultValue;
        protected final ConfigFieldSerializer<?> serializer;

        static @Nullable Parser seekParser(Object obj, ConfigManager manager){
            Parser parser = Parser.tryParse(obj, manager);
            if (parser == null){
                parser = ArrayParser.tryParse(obj, manager);
            }
            if (parser == null){
                parser = CollectionParser.tryParse(obj, manager);
            }
            if (parser == null){
                parser = MapParser.tryParse(obj, manager);
            }
            return parser;
        }

        static Parser tryParse(Object obj, ConfigManager manager){
            for (ConfigFieldSerializer<?> serializer : ConfigManager.SERIALIZERS){
                try {
                    return manager.new Parser(serializer, serializer.objectToJson(obj));
                } catch (Exception ignored){
                }
            }
            return null;
        }

        Parser(ConfigFieldSerializer<?> serializer, JsonElement defaultValue){
            this.serializer = serializer;
            this.defaultValue = defaultValue;
        }

        Object readJson(@NotNull JsonElement element, String name) throws Exception {
            return this.serializer.jsonToObject(element, name);
        }

        JsonElement writeJson(Object obj) throws Exception {
            return this.serializer.objectToJson(obj);
        }
    }

    private class ArrayParser extends Parser {
        private final Parser subParser;
        private final int size;

        static Parser tryParse(Object obj, ConfigManager manager){
            try {
                Object obj1 = Array.get(obj, 0);
                int size = Array.getLength(obj);
                Parser parser = Parser.seekParser(obj1, manager);
                if (parser != null){
                    JsonArray array = new JsonArray();
                    for (int i = 0; i < size; i++){
                        array.add(parser.writeJson(Array.get(obj, i)));
                    }
                    return manager.new ArrayParser(parser, array, size);
                }
            } catch (Exception ignored){
            }
            return null;
        }

        ArrayParser(Parser subParser, JsonElement defaultValue, int size) {
            super(null, defaultValue);
            this.subParser = subParser;
            this.size = size;
        }

        @Override
        Object readJson(@NotNull JsonElement element, String name) throws Exception {
            List<Object> raw = new ArrayList<>();
            int i = 0;
            for (JsonElement element1 : JsonHelper.asArray(element, name)){
                raw.add(this.subParser.readJson(element1, name));
                i++;
                if (i >= this.size){
                    break;
                }
            }
            if (i == 0){
                throw new JsonSyntaxException("Missing " + element + ", expected to find a JsonArray");
            }
            for (; i < this.size; i++){
                raw.add(raw.get(i - 1));
            }
            return raw.toArray();
        }

        @Override
        JsonElement writeJson(Object obj){
            JsonArray array = new JsonArray();
            for (int i = 0; i < this.size; i++){
                try {
                    array.add(this.subParser.writeJson(Array.get(obj, i)));
                }catch (Exception e){
                    break;
                }
            }
            return array;
        }
    }

    private class CollectionParser extends Parser {
        private final Parser subParser;

        static Parser tryParse(Object obj, ConfigManager manager){
            try {
                if (obj instanceof Collection<?> collection){
                    Object obj1 = collection.iterator().next();
                    Parser parser = Parser.seekParser(obj1, manager);
                    if (parser != null){
                        JsonArray jsonArray = new JsonArray();
                        for (Object o : collection){
                            try {
                                jsonArray.add(parser.writeJson(o));
                            }catch (Exception ignored){
                            }
                        }
                        return manager.new CollectionParser(parser, jsonArray);
                    }
                }
            } catch (Exception ignored) {
            }
            return null;
        }

        CollectionParser(Parser subParser, JsonElement defaultValue) {
            super(null, defaultValue);
            this.subParser = subParser;
        }

        @Override
        Object readJson(@NotNull JsonElement element, String name) throws Exception {
            List<Object> raw = new ArrayList<>();
            JsonArray array = JsonHelper.asArray(element, name);
            for (JsonElement element1 : array) {
                raw.add(this.subParser.readJson(element1, name));
            }
            return raw;
        }

        @Override
        JsonElement writeJson(Object obj) throws Exception {
            JsonArray array = new JsonArray();
            for (Object obj1 : (Collection<?>)obj){
                array.add(this.subParser.writeJson(obj1));
            }
            return array;
        }
    }

    private class MapParser extends Parser {
        private final Parser keyParser;
        private final Parser valueParser;

        static Parser tryParse(Object obj, ConfigManager manager){
            try {
                if (obj instanceof Map<?,?> map){
                    Map.Entry<?,?> entry = map.entrySet().iterator().next();
                    Object key = entry.getKey();
                    Parser keyParser = Parser.seekParser(key, manager);
                    if (keyParser != null ){
                        Object value = entry.getValue();
                        Parser valueParser = Parser.seekParser(value, manager);
                        if (valueParser != null){
                            JsonObject object = new JsonObject();
                            for (Map.Entry<?,?> entry1 : map.entrySet()){
                                object.add(keyParser.writeJson(entry1.getKey()).toString(), valueParser.writeJson(entry1.getValue()));
                            }
                            return manager.new MapParser(keyParser, valueParser, object);
                        }
                    }
                }
            } catch (Exception ignored) {
            }
            return null;
        }

        MapParser(Parser keyParser, Parser valueParser, JsonElement defaultValue) {
            super(null, defaultValue);
            this.keyParser = keyParser;
            this.valueParser = valueParser;
        }

        @Override
        Object readJson(@NotNull JsonElement element, String name) throws Exception {
            Map<Object, Object> raw = new HashMap<>();
            JsonObject object = JsonHelper.asObject(element, name);
            for (Map.Entry<String, JsonElement> entry : object.entrySet()){
                raw.put(this.keyParser.readJson(JsonParser.parseString(entry.getKey()), name), this.valueParser.readJson(entry.getValue(), name));
            }
            return raw;
        }

        @Override
        JsonElement writeJson(Object obj) throws Exception {
            JsonObject object = new JsonObject();
            for (Map.Entry<?, ?> entry : ((Map<?, ?>)obj).entrySet()){
                JsonElement keyElement = this.keyParser.writeJson(entry.getKey());
                String key;
                try {
                    key = keyElement.getAsString();
                }catch (Exception e){
                    key = keyElement.toString();
                }
                object.add(key, this.valueParser.writeJson(entry.getValue()));
            }
            return object;
        }
    }


}
